package com.kexio.pay.util;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.kexio.pay.enums.SignType;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 微信支付工具类（参考IJPay实现）
 * 
 * @author Kexio Team
 */
public class WxPayKit {
    
    private static final Logger logger = LoggerFactory.getLogger(WxPayKit.class);
    private static final String FIELD_SIGN = "sign";
    private static final String FIELD_SIGN_TYPE = "sign_type";
    
    /**
     * 生成随机字符串
     */
    public static String generateStr() {
        return RandomUtil.randomString(32);
    }
    
    /**
     * MD5签名
     */
    public static String md5(String data) {
        return DigestUtils.md5Hex(data);
    }
    
    /**
     * HMAC-SHA256签名
     */
    public static String hmacSha256(String data, String key) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hash);
        } catch (Exception e) {
            throw new RuntimeException("HMAC-SHA256签名失败", e);
        }
    }
    
    /**
     * 字节数组转十六进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString().toUpperCase();
    }
    
    /**
     * 生成签名（参考IJPay）
     * 
     * @param params 参数Map
     * @param partnerKey API密钥
     * @param signType 签名类型
     * @return 签名字符串
     */
    public static String createSign(Map<String, String> params, String partnerKey, SignType signType) {
        // 1. 移除sign字段
        params.remove(FIELD_SIGN);
        
        // 2. 按参数名ASCII码排序
        String signContent = createSignContent(params);
        
        // 3. 拼接密钥
        signContent = signContent + "&key=" + partnerKey;
        
        // 4. 根据签名类型选择算法
        String sign;
        if (SignType.HMACSHA256.equals(signType)) {
            sign = hmacSha256(signContent, partnerKey);
        } else {
            sign = md5(signContent);
        }
        
        return sign.toUpperCase();
    }
    
    /**
     * 创建签名内容（参数排序并拼接）
     */
    public static String createSignContent(Map<String, String> params) {
        // 过滤空值和sign字段
        Map<String, String> filteredParams = new TreeMap<>();
        params.forEach((key, value) -> {
            if (StrUtil.isNotBlank(value) && !FIELD_SIGN.equals(key) && !FIELD_SIGN_TYPE.equals(key)) {
                filteredParams.put(key, value);
            }
        });
        
        // 拼接成 key1=value1&key2=value2 格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : filteredParams.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        
        return sb.toString();
    }
    
    /**
     * 验证签名（用于回调）
     */
    public static boolean verifySign(Map<String, String> params, String partnerKey, SignType signType) {
        String sign = params.get(FIELD_SIGN);
        if (StrUtil.isBlank(sign)) {
            logger.warn("签名字段为空");
            return false;
        }
        
        String localSign = createSign(params, partnerKey, signType);
        return sign.equals(localSign);
    }
    
    /**
     * Map转XML
     */
    public static String mapToXml(Map<String, String> params) {
        StringBuilder xml = new StringBuilder("<xml>");
        params.forEach((key, value) -> {
            if (StrUtil.isNotBlank(value)) {
                xml.append("<").append(key).append(">")
                   .append("<![CDATA[").append(value).append("]]>")
                   .append("</").append(key).append(">");
            }
        });
        xml.append("</xml>");
        return xml.toString();
    }
    
    /**
     * XML转Map（参考IJPay）
     */
    public static Map<String, String> xmlToMap(String xmlStr) {
        Map<String, String> map = new HashMap<>();
        
        try {
            InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            
            // 防止XXE攻击
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            factory.setXIncludeAware(false);
            factory.setExpandEntityReferences(false);
            
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);
            document.getDocumentElement().normalize();
            
            NodeList nodeList = document.getDocumentElement().getChildNodes();
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    map.put(element.getNodeName(), element.getTextContent());
                }
            }
            
        } catch (Exception e) {
            logger.error("XML解析失败: {}", xmlStr, e);
            throw new RuntimeException("XML解析失败", e);
        }
        
        return map;
    }
    
    /**
     * 检查返回码是否为SUCCESS
     */
    public static boolean isSuccess(String code) {
        return "SUCCESS".equals(code);
    }
    
    /**
     * 检查返回码和结果码
     */
    public static boolean isSuccess(Map<String, String> result) {
        String returnCode = result.get("return_code");
        String resultCode = result.get("result_code");
        return isSuccess(returnCode) && isSuccess(resultCode);
    }
}

